Posts tagged with 'asp.net mvc'
Ack, WebForms! Burn it with fire!
WebForms is known to be a horrible ghetto full of HTML pitfalls, a Kafka-esque lifecycle, testability nightmares, and pages served up with inflated ViewState data.
Some of that is still true, but WebForms has improved, and despite all the horrors that we're familiar with, it's still got a lot going for it, including a control tree that can be very useful at times.
I have been working on a WebForms project in my independent consulting practice (it sounds so fancy when I say it like that). Even though it's a relatively small project, I was still thinking about how best to introduce dependency inversion [PDF] to the project (or if I even should).
I googled around, sifted through a lot of ancient blog posts, and I came up with a relatively simple solution with my favorite IoC tool, StructureMap. It's not really optimal, or terribly elegant, but it just might get the job done and still allow us to write tests. I'm certainly open to alternatives, but because this is a small project, I don't want to spend a ton of billable hours on a solution, especially when I'll be turning the code over to novice programmer(s) when my time is done.
First, I simply add standard StructureMap config to the project. Application_Start in Global.asax is as good a place as any. I also use the default convention (if you aren't familiar, it's basically naming the interface by prepending an "I" to the concrete class name).
Okay, so that's pretty standard. Now let's approach from the other end: an actual code-behind class of an ASPX page. I can't use constructor injection here, because I don't have much control over this object being instantiated. Instead, I'll just make public properties for my service(s).
So now I've got a StructureMap defintion, and I've got properties that want to be set by StructureMap. But how do I get them talking? My solution was to introduce a base class (bleck) to the WebForms page. In this constructor, I'll use StructureMap's BuildUp method to populate properties (I have to specify which ones, as noted later on).
That's the ugliest part, as far as I'm concerned, but I couldn't think of a better solution. Maybe an HttpModule or something? Maybe the newer WebForms releases contain something that I'm not familar with yet? Leave a comment with your suggestions.
Finally, StructureMap just won't go and set every property by itself. You need to specify which ones. There are a couple of ways to do this. One is the SetAllProperties, which allows you to conditionally examine the PropertyInfo, and the other is FillAllPropertiesOfType, which you can use to directly specify which properties to set based on what type they are. Even though it's a small project, I'd would prefer to use SetAllProperties, so I don't have to go back and edit my StructureMap config each time I create a new service or a new WebForms page.
This week, there's a Manning Publishing promotion brought to you by my friends at PostSharp! This week of deals will bring you 50% off of a whole bunch of great .NET and/or AOP related books, including mine. You'll also get free excerpts from each book, so you can try before you buy.
Today, you can get my book, AOP in .NET, for 50% off, using code dotd0819au.
These are the Promotional Codes that will be active all week:
Day One: Save 50% on C# in Depth, Third Edition and Real-World Functional Programming. Enter pswkd1 in the Promotional Code box when you check out. Expires midnight ET August 20. Only at manning.com.
Day Two: Save 50% on AOP in .NET and AspectJ in Action, Second Edition. Enter pswkd2 in the Promotional Code box when you check out. Expires midnight ET August 21. Only at manning.com.
Day Three: Save 50% on ASP.NET MVC 4 in Action and ASP.NET 4.0 in Practice. Enter pswkd3 in the Promotional Code box when you check out. Expires midnight ET Aug 22. Only at manning.com.
Day Four: Save 50% on Metaprogramming in .NET and DSLs in Boo. Enter pswkd4 in the Promotional Code box when you check out. Expires midnight ET Aug 23. Only at manning.com.
Day Five: Save 50% on Brownfield Application Development in .NET and Continuous Integration in .NET. Enter pswkd5 in the Promotional Code box when you check out. Expires midnight ET Aug 24. Only at manning.com.
Day Six: Save 50% on Windows Store App Development and Windows 8 Apps with HTML5 and JavaScript. Enter pswkd6 in the Promotional Code box when you check out. Expires midnight ET Aug 25. Only at manning.com.
So, if you are a .NET developer, a web developer, or just trying to learn some more skills, this is the week for you!
This is the last post of a series of posts about using ASP.NET's ActionFilter.
The last method that you can override in an ActionFilter is OnResultExecute, which runs after the result of a controller action has been executed.
An argument of type ResultExecutedContext gets passed in to OnResultExecute. It's not wildly different from ResultExecutingContext, except that instead of a "Cancel" member, you get a "Canceled" member, which just tells you after the fact if the action was cancelled or not. You can still access HttpContext, the Controller object, the Result, the RouteData, etc.
I can't really create another kitchen sink view like I could with the previous blog posts, because the ActionResult (usually a ViewResult) has already been executed. (I could still write to the Http Response, but that would be a little tedious).
Since OnResultExecuted can't output to the view, it's somewhat limited in its general usefulness. Logging, of course, can still be done here. Last minute changes to the HttpResponse might be a good use of this filter. You can still handle some exceptions, though using a HandleErrorAttribute filter might be a better idea for that.
Continuing my series on ASP.NET MVC ActionFilters, this time it's about the OnResultExecuting method (which runs before the result of the controller (e.g. ActionResult, ViewResult, etc) is run.
Since this filter runs after the action itself has completed, you won't be able to access any information about the action (except Route Data). The ResultExecutingContext has the basic context information that you've seen in the other contexts (HttpContext, Controller, RouteData), but it also has a "Cancel" bool property. If you set this property to true, the Result will not be executed (e.g. if it's a ViewResult, the View will not be returned to the browser; if it's a Redirect result, redirect headers will not be returned to the browser, etc). What you'll get by default is just a blank response with status code 200.
So why 'cancel' a result? You could substitute a cached view or replace it with some other response by directly manipulating the HttpContext. If the action returned a ViewResult, JsonResult, etc, you can still access the model passed to that result, to perform some logic or manipulate its properties.
As before, I created a kitchen sink view of ResultExecutingContext just for reference. I decided to put it in the ViewBag this time:
And here's a screenshot of the results in my browser:
One more to go in this series. As always, if there's something that you want more information or details on, feel free to leave a comment or send a contact form to me, and I'll be glad to help out in any way I can.
Someone asked in a comment on my post about OnActionExecuting if one could get access to the model in these filters in order to record some audit data. I think this is probably a good use-case for using PostSharp or Castle DynamicProxy to write an audit aspect and apply it on your service classes or repositories.
But suppose you wanted to record certain fields and other audit information about information being modified in your controller. I wasn't sure if this was going to work, so I coded something up in 30 minutes, and much to my surprise it worked pretty good.
It's too long to embed here, but here's the Gist link to an Audit ActionFilter proof of concept. To use this attribute, simply decorate the actions you want to audit like: [Audit(typeof(SomeEntity), "EntityPropertyName1", "EntityPropertyName2"]. The filter will first check to make sure the incoming entity results in a valid ModelState. If it's not, then no reason to record an audit record because you aren't persisting changes yet (maybe the user missed a required field).
If it is valid, then it will interrogate the parameters and arguments to see if it can find one of type SomeEntity. If it does, it will then interrogate the properties of that entity to get all the values of the properties that are named EntityPropertyName1, EntityPropertyName2, etc. It will then record all this information, along with other audit information like action name, controller name, the current user, timestamp. All this "interrogation" is being done via reflection.
In my example, it just writes it to TempData so that it can be displayed back out. In a real example, it would write it to whatever data store you are using for your audit records (a database, for instance).
Problems with this method of auditing:
- Reflection - it's slow and brittle. This version is very simple and it's still quite messy. A real production version could be downright unwieldy
- Line 61 of CustomerController.cs - I'm making the assumption that a ToString on each entity's property is meaningful. That may not always be the case, which could lead to even more complexity.
- If you spell one of the field names wrong, you could have problems. Refactoring/renaming of your entity properties could break this quite easily.
That being said, if you have a very large system and audit requirements like this, then this messy approach could be better than the even messier alternative of boilerplate audit code in every controller action. I still think using PostSharp or DynamicProxy on the entity repository/service layer would be a cleaner approach.
I contacted Nestor with my proof-of-concept, and he had some comments about possible further refinements:
- Store the name of the class and properties to be audited in a class/model of its own, so an administrator could decide which properties to audit via some administration tool.
- Store the property/value pairs audited in a XML format in the model, facilitating the search of some values (what were the activities of a given user, who or when a given class was deleted o modified, etc.) using the abilities of SQL Server in making indexes over XML columns.